{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tutorial on Hand Written Digit Recognition\n",
    "\n",
    "In this tutorial we will go through the basic use case of MXNet and also touch on some advanced usages. This example is based on the MNIST dataset, which contains 70,000 images of hand written characters with 28-by-28 pixel size.\n",
    "\n",
    "This tutorial covers the following topics:\n",
    "- network definition.\n",
    "- Variable naming.\n",
    "- Basic data loading and training with feed-forward deep neural networks.\n",
    "- Monitoring intermediate outputs for debuging.\n",
    "- Custom training loop for advanced models.\n",
    "\n",
    "First let's import the modules and setup logging:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import mxnet as mx\n",
    "import numpy as np\n",
    "import cv2\n",
    "import matplotlib.pyplot as plt\n",
    "import logging\n",
    "\n",
    "logger = logging.getLogger()\n",
    "logger.setLevel(logging.DEBUG)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## Network Definition\n",
    "Now we can start constructing our network:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# Variables are place holders for input arrays. We give each variable a unique name.\n",
    "data = mx.symbol.Variable('data')\n",
    "\n",
    "# The input is fed to a fully connected layer that computes Y=WX+b.\n",
    "# This is the main computation module in the network.\n",
    "# Each layer also needs an unique name. We'll talk more about naming in the next section.\n",
    "fc1  = mx.symbol.FullyConnected(data = data, name='fc1', num_hidden=128)\n",
    "# Activation layers apply a non-linear function on the previous layer's output.\n",
    "# Here we use Rectified Linear Unit (ReLU) that computes Y = max(X, 0).\n",
    "act1 = mx.symbol.Activation(data = fc1, name='relu1', act_type=\"relu\")\n",
    "\n",
    "fc2  = mx.symbol.FullyConnected(data = act1, name = 'fc2', num_hidden = 64)\n",
    "act2 = mx.symbol.Activation(data = fc2, name='relu2', act_type=\"relu\")\n",
    "\n",
    "fc3  = mx.symbol.FullyConnected(data = act2, name='fc3', num_hidden=10)\n",
    "# Finally we have a loss layer that compares the network's output with label and generates gradient signals.\n",
    "mlp  = mx.symbol.SoftmaxOutput(data = fc3, name = 'softmax')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can visualize the network we just defined with MXNet's visualization module:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/svg+xml": [
       "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
       "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
       " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
       "<!-- Generated by graphviz version 2.36.0 (20140111.2315)\n",
       " -->\n",
       "<!-- Title: plot Pages: 1 -->\n",
       "<svg width=\"102pt\" height=\"536pt\"\n",
       " viewBox=\"0.00 0.00 102.00 536.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
       "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 532)\">\n",
       "<title>plot</title>\n",
       "<polygon fill=\"white\" stroke=\"none\" points=\"-4,4 -4,-532 98,-532 98,4 -4,4\"/>\n",
       "<!-- fc1 -->\n",
       "<g id=\"node1\" class=\"node\"><title>fc1</title>\n",
       "<polygon fill=\"#fb8072\" stroke=\"black\" points=\"94,-58 -7.10543e-15,-58 -7.10543e-15,-3.55271e-15 94,-3.55271e-15 94,-58\"/>\n",
       "<text text-anchor=\"middle\" x=\"47\" y=\"-32.8\" font-family=\"Times,serif\" font-size=\"14.00\">FullyConnected</text>\n",
       "<text text-anchor=\"middle\" x=\"47\" y=\"-17.8\" font-family=\"Times,serif\" font-size=\"14.00\">128</text>\n",
       "</g>\n",
       "<!-- relu1 -->\n",
       "<g id=\"node2\" class=\"node\"><title>relu1</title>\n",
       "<polygon fill=\"#ffffb3\" stroke=\"black\" points=\"94,-152 -7.10543e-15,-152 -7.10543e-15,-94 94,-94 94,-152\"/>\n",
       "<text text-anchor=\"middle\" x=\"47\" y=\"-126.8\" font-family=\"Times,serif\" font-size=\"14.00\">Activation</text>\n",
       "<text text-anchor=\"middle\" x=\"47\" y=\"-111.8\" font-family=\"Times,serif\" font-size=\"14.00\">relu</text>\n",
       "</g>\n",
       "<!-- relu1&#45;&gt;fc1 -->\n",
       "<g id=\"edge1\" class=\"edge\"><title>relu1&#45;&gt;fc1</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M47,-83.7443C47,-75.2043 47,-66.2977 47,-58.2479\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"47,-93.8971 42.5001,-83.897 47,-88.8971 47.0001,-83.8971 47.0001,-83.8971 47.0001,-83.8971 47,-88.8971 51.5001,-83.8971 47,-93.8971 47,-93.8971\"/>\n",
       "</g>\n",
       "<!-- fc2 -->\n",
       "<g id=\"node3\" class=\"node\"><title>fc2</title>\n",
       "<polygon fill=\"#fb8072\" stroke=\"black\" points=\"94,-246 -7.10543e-15,-246 -7.10543e-15,-188 94,-188 94,-246\"/>\n",
       "<text text-anchor=\"middle\" x=\"47\" y=\"-220.8\" font-family=\"Times,serif\" font-size=\"14.00\">FullyConnected</text>\n",
       "<text text-anchor=\"middle\" x=\"47\" y=\"-205.8\" font-family=\"Times,serif\" font-size=\"14.00\">64</text>\n",
       "</g>\n",
       "<!-- fc2&#45;&gt;relu1 -->\n",
       "<g id=\"edge2\" class=\"edge\"><title>fc2&#45;&gt;relu1</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M47,-177.744C47,-169.204 47,-160.298 47,-152.248\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"47,-187.897 42.5001,-177.897 47,-182.897 47.0001,-177.897 47.0001,-177.897 47.0001,-177.897 47,-182.897 51.5001,-177.897 47,-187.897 47,-187.897\"/>\n",
       "</g>\n",
       "<!-- relu2 -->\n",
       "<g id=\"node4\" class=\"node\"><title>relu2</title>\n",
       "<polygon fill=\"#ffffb3\" stroke=\"black\" points=\"94,-340 -7.10543e-15,-340 -7.10543e-15,-282 94,-282 94,-340\"/>\n",
       "<text text-anchor=\"middle\" x=\"47\" y=\"-314.8\" font-family=\"Times,serif\" font-size=\"14.00\">Activation</text>\n",
       "<text text-anchor=\"middle\" x=\"47\" y=\"-299.8\" font-family=\"Times,serif\" font-size=\"14.00\">relu</text>\n",
       "</g>\n",
       "<!-- relu2&#45;&gt;fc2 -->\n",
       "<g id=\"edge3\" class=\"edge\"><title>relu2&#45;&gt;fc2</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M47,-271.744C47,-263.204 47,-254.298 47,-246.248\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"47,-281.897 42.5001,-271.897 47,-276.897 47.0001,-271.897 47.0001,-271.897 47.0001,-271.897 47,-276.897 51.5001,-271.897 47,-281.897 47,-281.897\"/>\n",
       "</g>\n",
       "<!-- fc3 -->\n",
       "<g id=\"node5\" class=\"node\"><title>fc3</title>\n",
       "<polygon fill=\"#fb8072\" stroke=\"black\" points=\"94,-434 -7.10543e-15,-434 -7.10543e-15,-376 94,-376 94,-434\"/>\n",
       "<text text-anchor=\"middle\" x=\"47\" y=\"-408.8\" font-family=\"Times,serif\" font-size=\"14.00\">FullyConnected</text>\n",
       "<text text-anchor=\"middle\" x=\"47\" y=\"-393.8\" font-family=\"Times,serif\" font-size=\"14.00\">10</text>\n",
       "</g>\n",
       "<!-- fc3&#45;&gt;relu2 -->\n",
       "<g id=\"edge4\" class=\"edge\"><title>fc3&#45;&gt;relu2</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M47,-365.744C47,-357.204 47,-348.298 47,-340.248\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"47,-375.897 42.5001,-365.897 47,-370.897 47.0001,-365.897 47.0001,-365.897 47.0001,-365.897 47,-370.897 51.5001,-365.897 47,-375.897 47,-375.897\"/>\n",
       "</g>\n",
       "<!-- softmax -->\n",
       "<g id=\"node6\" class=\"node\"><title>softmax</title>\n",
       "<polygon fill=\"#fccde5\" stroke=\"black\" points=\"94,-528 -7.10543e-15,-528 -7.10543e-15,-470 94,-470 94,-528\"/>\n",
       "<text text-anchor=\"middle\" x=\"47\" y=\"-495.3\" font-family=\"Times,serif\" font-size=\"14.00\">SoftmaxOutput</text>\n",
       "</g>\n",
       "<!-- softmax&#45;&gt;fc3 -->\n",
       "<g id=\"edge5\" class=\"edge\"><title>softmax&#45;&gt;fc3</title>\n",
       "<path fill=\"none\" stroke=\"black\" d=\"M47,-459.744C47,-451.204 47,-442.298 47,-434.248\"/>\n",
       "<polygon fill=\"black\" stroke=\"black\" points=\"47,-469.897 42.5001,-459.897 47,-464.897 47.0001,-459.897 47.0001,-459.897 47.0001,-459.897 47,-464.897 51.5001,-459.897 47,-469.897 47,-469.897\"/>\n",
       "</g>\n",
       "</g>\n",
       "</svg>\n"
      ],
      "text/plain": [
       "<graphviz.dot.Digraph at 0x7f20eae96510>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mx.viz.plot_network(mlp)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Variable Naming\n",
    "\n",
    "MXNet requires variable names to follow certain conventions:\n",
    "- All input arrays have a name. This includes inputs (data & label) and model parameters (weight, bias, etc).\n",
    "- Arrays can be renamed by creating named variable. Otherwise, a default name is given as 'SymbolName_ArrayName'. For example, FullyConnected symbol fc1's weight array is named as 'fc1_weight'.\n",
    "- Although you can also rename weight arrays with variables, weight array's name should always end with '_weight' and bias array '_bias'. MXNet relies on the suffixes of array names to correctly initialize & update them.\n",
    "\n",
    "Call list_arguments method on a symbol to get the names of all its inputs:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['data',\n",
       " 'fc1_weight',\n",
       " 'fc1_bias',\n",
       " 'fc2_weight',\n",
       " 'fc2_bias',\n",
       " 'fc3_weight',\n",
       " 'fc3_bias',\n",
       " 'softmax_label']"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mlp.list_arguments()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Data Loading\n",
    "\n",
    "We fetch and load the MNIST dataset and partition it into two sets: 60000 examples for training and 10000 examples for testing. We also visualize a few examples to get an idea of what the dataset looks like."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\n58aS+q+DnZwAAAAASUVORK5CYII=\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x7f215433ea10>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from sklearn.datasets import fetch_mldata\n",
    "mnist = fetch_mldata('MNIST original')\n",
    "np.random.seed(1234) # set seed for deterministic ordering\n",
    "p = np.random.permutation(mnist.data.shape[0])\n",
    "X = mnist.data[p]\n",
    "Y = mnist.target[p]\n",
    "\n",
    "for i in range(10):\n",
    "    plt.subplot(1,10,i+1)\n",
    "    plt.imshow(X[i].reshape((28,28)), cmap='Greys_r')\n",
    "    plt.axis('off')\n",
    "plt.show()\n",
    "\n",
    "X = X.astype(np.float32)/255\n",
    "X_train = X[:60000]\n",
    "X_test = X[60000:]\n",
    "Y_train = Y[:60000]\n",
    "Y_test = Y[60000:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we can create data iterators from our MNIST data. A data iterator returns a batch of data examples each time for the network to process. MXNet provide a suite of basic DataIters for parsing different data format. Here we use NDArrayIter, which wraps around a numpy array and each time slice a chunk from it along the first dimension."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "batch_size = 100\n",
    "train_iter = mx.io.NDArrayIter(X_train, Y_train, batch_size=batch_size)\n",
    "test_iter = mx.io.NDArrayIter(X_test, Y_test, batch_size=batch_size)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Training\n",
    "\n",
    "With the network and data source defined, we can finally start to train our model. We do this with MXNet's convenience wrapper for feed forward neural networks (it can also be made to handle RNNs with explicit unrolling). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:root:Start training with [gpu(0)]\n",
      "INFO:root:Epoch[0] Batch [200]\tSpeed: 69175.44 samples/sec\tTrain-accuracy=0.389050\n",
      "INFO:root:Epoch[0] Batch [400]\tSpeed: 70879.78 samples/sec\tTrain-accuracy=0.903850\n",
      "INFO:root:Epoch[0] Batch [600]\tSpeed: 70539.99 samples/sec\tTrain-accuracy=0.937100\n",
      "INFO:root:Epoch[0] Resetting Data Iterator\n",
      "INFO:root:Epoch[0] Time cost=1.119\n",
      "INFO:root:Epoch[0] Validation-accuracy=0.950800\n",
      "INFO:root:Epoch[1] Batch [200]\tSpeed: 70849.61 samples/sec\tTrain-accuracy=0.947300\n",
      "INFO:root:Epoch[1] Batch [400]\tSpeed: 70369.21 samples/sec\tTrain-accuracy=0.961350\n",
      "INFO:root:Epoch[1] Batch [600]\tSpeed: 70205.72 samples/sec\tTrain-accuracy=0.963700\n",
      "INFO:root:Epoch[1] Resetting Data Iterator\n",
      "INFO:root:Epoch[1] Time cost=0.857\n",
      "INFO:root:Epoch[1] Validation-accuracy=0.960600\n",
      "INFO:root:Epoch[2] Batch [200]\tSpeed: 70656.44 samples/sec\tTrain-accuracy=0.965600\n",
      "INFO:root:Epoch[2] Batch [400]\tSpeed: 70186.51 samples/sec\tTrain-accuracy=0.971100\n",
      "INFO:root:Epoch[2] Batch [600]\tSpeed: 70212.13 samples/sec\tTrain-accuracy=0.972400\n",
      "INFO:root:Epoch[2] Resetting Data Iterator\n",
      "INFO:root:Epoch[2] Time cost=0.858\n",
      "INFO:root:Epoch[2] Validation-accuracy=0.965300\n",
      "INFO:root:Epoch[3] Batch [200]\tSpeed: 70510.46 samples/sec\tTrain-accuracy=0.973100\n",
      "INFO:root:Epoch[3] Batch [400]\tSpeed: 70383.14 samples/sec\tTrain-accuracy=0.977300\n",
      "INFO:root:Epoch[3] Batch [600]\tSpeed: 71392.10 samples/sec\tTrain-accuracy=0.979250\n",
      "INFO:root:Epoch[3] Resetting Data Iterator\n",
      "INFO:root:Epoch[3] Time cost=0.854\n",
      "INFO:root:Epoch[3] Validation-accuracy=0.969300\n",
      "INFO:root:Epoch[4] Batch [200]\tSpeed: 70488.59 samples/sec\tTrain-accuracy=0.980000\n",
      "INFO:root:Epoch[4] Batch [400]\tSpeed: 69089.86 samples/sec\tTrain-accuracy=0.981450\n",
      "INFO:root:Epoch[4] Batch [600]\tSpeed: 69732.54 samples/sec\tTrain-accuracy=0.982200\n",
      "INFO:root:Epoch[4] Resetting Data Iterator\n",
      "INFO:root:Epoch[4] Time cost=0.865\n",
      "INFO:root:Epoch[4] Validation-accuracy=0.971000\n",
      "INFO:root:Epoch[5] Batch [200]\tSpeed: 70675.91 samples/sec\tTrain-accuracy=0.982950\n",
      "INFO:root:Epoch[5] Batch [400]\tSpeed: 70601.79 samples/sec\tTrain-accuracy=0.984400\n",
      "INFO:root:Epoch[5] Batch [600]\tSpeed: 69826.80 samples/sec\tTrain-accuracy=0.984700\n",
      "INFO:root:Epoch[5] Resetting Data Iterator\n",
      "INFO:root:Epoch[5] Time cost=0.857\n",
      "INFO:root:Epoch[5] Validation-accuracy=0.969800\n",
      "INFO:root:Epoch[6] Batch [200]\tSpeed: 69092.77 samples/sec\tTrain-accuracy=0.985200\n",
      "INFO:root:Epoch[6] Batch [400]\tSpeed: 66663.98 samples/sec\tTrain-accuracy=0.986800\n",
      "INFO:root:Epoch[6] Batch [600]\tSpeed: 68948.41 samples/sec\tTrain-accuracy=0.987400\n",
      "INFO:root:Epoch[6] Resetting Data Iterator\n",
      "INFO:root:Epoch[6] Time cost=0.884\n",
      "INFO:root:Epoch[6] Validation-accuracy=0.971500\n",
      "INFO:root:Epoch[7] Batch [200]\tSpeed: 69331.52 samples/sec\tTrain-accuracy=0.986400\n",
      "INFO:root:Epoch[7] Batch [400]\tSpeed: 70849.91 samples/sec\tTrain-accuracy=0.987500\n",
      "INFO:root:Epoch[7] Batch [600]\tSpeed: 70912.20 samples/sec\tTrain-accuracy=0.989650\n",
      "INFO:root:Epoch[7] Resetting Data Iterator\n",
      "INFO:root:Epoch[7] Time cost=0.858\n",
      "INFO:root:Epoch[7] Validation-accuracy=0.972400\n",
      "INFO:root:Epoch[8] Batch [200]\tSpeed: 69788.06 samples/sec\tTrain-accuracy=0.986250\n",
      "INFO:root:Epoch[8] Batch [400]\tSpeed: 69414.77 samples/sec\tTrain-accuracy=0.988250\n",
      "INFO:root:Epoch[8] Batch [600]\tSpeed: 69092.99 samples/sec\tTrain-accuracy=0.990100\n",
      "INFO:root:Epoch[8] Resetting Data Iterator\n",
      "INFO:root:Epoch[8] Time cost=0.869\n",
      "INFO:root:Epoch[8] Validation-accuracy=0.977000\n",
      "INFO:root:Epoch[9] Batch [200]\tSpeed: 69202.95 samples/sec\tTrain-accuracy=0.990950\n",
      "INFO:root:Epoch[9] Batch [400]\tSpeed: 69427.58 samples/sec\tTrain-accuracy=0.988450\n",
      "INFO:root:Epoch[9] Batch [600]\tSpeed: 70445.32 samples/sec\tTrain-accuracy=0.990050\n",
      "INFO:root:Epoch[9] Resetting Data Iterator\n",
      "INFO:root:Epoch[9] Time cost=0.866\n",
      "INFO:root:Epoch[9] Validation-accuracy=0.973300\n"
     ]
    }
   ],
   "source": [
    "model = mx.model.FeedForward(\n",
    "    ctx = mx.gpu(0),      # Run on GPU 0\n",
    "    symbol = mlp,         # Use the network we just defined\n",
    "    num_epoch = 10,       # Train for 10 epochs\n",
    "    learning_rate = 0.1,  # Learning rate\n",
    "    momentum = 0.9,       # Momentum for SGD with momentum\n",
    "    wd = 0.00001)         # Weight decay for regularization\n",
    "model.fit(\n",
    "    X=train_iter,  # Training data set\n",
    "    eval_data=test_iter,  # Testing data set. MXNet computes scores on test set every epoch\n",
    "    batch_end_callback = mx.callback.Speedometer(batch_size, 200))  # Logging module to print out progress"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Evaluation\n",
    "\n",
    "After the model is trained, we can evaluate it on a held out test set.\n",
    "First, lets classity a sample image:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\nQmCC\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x7f20c554e3d0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Result: 7\n"
     ]
    }
   ],
   "source": [
    "plt.imshow((X_test[0].reshape((28,28))*255).astype(np.uint8), cmap='Greys_r')\n",
    "plt.show()\n",
    "print 'Result:', model.predict(X_test[0:1])[0].argmax()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also evaluate the model's accuracy on the entire test set:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false,
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy: 97.33 %\n"
     ]
    }
   ],
   "source": [
    "print 'Accuracy:', model.score(test_iter)*100, '%'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Now, try if your model recognizes your own hand writing.\n",
    "\n",
    "Write a digit from 0 to 9 in the box below. Try to put your digit in the middle of the box."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false,
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<style type=\"text/css\">canvas { border: 1px solid black; }</style><div id=\"board\"><canvas id=\"myCanvas\" width=\"100px\" height=\"100px\">Sorry, your browser doesn't support canvas technology.</canvas><p><button id=\"classify\" onclick=\"classify()\">Classify</button><button id=\"clear\" onclick=\"myClear()\">Clear</button>Result: <input type=\"text\" id=\"result_output\" size=\"5\" value=\"\"></p></div><script type=\"text/JavaScript\" src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js?ver=1.4.2\"></script><script type=\"text/javascript\">function init() {var myCanvas = document.getElementById(\"myCanvas\");var curColor = $('#selectColor option:selected').val();if(myCanvas){var isDown = false;var ctx = myCanvas.getContext(\"2d\");var canvasX, canvasY;ctx.lineWidth = 8;$(myCanvas).mousedown(function(e){isDown = true;ctx.beginPath();var parentOffset = $(this).parent().offset(); canvasX = e.pageX - parentOffset.left;canvasY = e.pageY - parentOffset.top;ctx.moveTo(canvasX, canvasY);}).mousemove(function(e){if(isDown != false) {var parentOffset = $(this).parent().offset(); canvasX = e.pageX - parentOffset.left;canvasY = e.pageY - parentOffset.top;ctx.lineTo(canvasX, canvasY);ctx.strokeStyle = curColor;ctx.stroke();}}).mouseup(function(e){isDown = false;ctx.closePath();});}$('#selectColor').change(function () {curColor = $('#selectColor option:selected').val();});}init();function handle_output(out) {document.getElementById(\"result_output\").value = out.content.data[\"text/plain\"];}function classify() {var kernel = IPython.notebook.kernel;var myCanvas = document.getElementById(\"myCanvas\");data = myCanvas.toDataURL('image/png');document.getElementById(\"result_output\").value = \"\";kernel.execute(\"classify('\" + data +\"')\",  { 'iopub' : {'output' : handle_output}}, {silent:false});}function myClear() {var myCanvas = document.getElementById(\"myCanvas\");myCanvas.getContext(\"2d\").clearRect(0, 0, myCanvas.width, myCanvas.height);}</script>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# run hand drawing test\n",
    "from IPython.display import HTML\n",
    "\n",
    "def classify(img):\n",
    "    img = img[len('data:image/png;base64,'):].decode('base64')\n",
    "    img = cv2.imdecode(np.fromstring(img, np.uint8), -1)\n",
    "    img = cv2.resize(img[:,:,3], (28,28))\n",
    "    img = img.astype(np.float32).reshape((1, 784))/255.0\n",
    "    return model.predict(img)[0].argmax()\n",
    "\n",
    "html = \"\"\"<style type=\"text/css\">canvas { border: 1px solid black; }</style><div id=\"board\"><canvas id=\"myCanvas\" width=\"100px\" height=\"100px\">Sorry, your browser doesn't support canvas technology.</canvas><p><button id=\"classify\" onclick=\"classify()\">Classify</button><button id=\"clear\" onclick=\"myClear()\">Clear</button>Result: <input type=\"text\" id=\"result_output\" size=\"5\" value=\"\"></p></div>\"\"\"\n",
    "script = \"\"\"<script type=\"text/JavaScript\" src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js?ver=1.4.2\"></script><script type=\"text/javascript\">function init() {var myCanvas = document.getElementById(\"myCanvas\");var curColor = $('#selectColor option:selected').val();if(myCanvas){var isDown = false;var ctx = myCanvas.getContext(\"2d\");var canvasX, canvasY;ctx.lineWidth = 8;$(myCanvas).mousedown(function(e){isDown = true;ctx.beginPath();var parentOffset = $(this).parent().offset(); canvasX = e.pageX - parentOffset.left;canvasY = e.pageY - parentOffset.top;ctx.moveTo(canvasX, canvasY);}).mousemove(function(e){if(isDown != false) {var parentOffset = $(this).parent().offset(); canvasX = e.pageX - parentOffset.left;canvasY = e.pageY - parentOffset.top;ctx.lineTo(canvasX, canvasY);ctx.strokeStyle = curColor;ctx.stroke();}}).mouseup(function(e){isDown = false;ctx.closePath();});}$('#selectColor').change(function () {curColor = $('#selectColor option:selected').val();});}init();function handle_output(out) {document.getElementById(\"result_output\").value = out.content.data[\"text/plain\"];}function classify() {var kernel = IPython.notebook.kernel;var myCanvas = document.getElementById(\"myCanvas\");data = myCanvas.toDataURL('image/png');document.getElementById(\"result_output\").value = \"\";kernel.execute(\"classify('\" + data +\"')\",  { 'iopub' : {'output' : handle_output}}, {silent:false});}function myClear() {var myCanvas = document.getElementById(\"myCanvas\");myCanvas.getContext(\"2d\").clearRect(0, 0, myCanvas.width, myCanvas.height);}</script>\"\"\"\n",
    "HTML(html+script)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Debugging\n",
    "\n",
    "DNNs can perform poorly for a lot of reasons, like learning rate too big/small, initialization too big/small, network structure not reasonable, etc. When this happens it's often helpful to print out the weights and intermediate outputs to understand what's going on. MXNet provides a monitor utility that does this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:root:Start training with [gpu(0)]\n",
      "INFO:root:Batch:       1 fc1_backward_weight            0.000519617\t\n",
      "INFO:root:Batch:       1 fc1_weight                     0.00577777\t\n",
      "INFO:root:Batch:       1 fc2_backward_weight            0.00164324\t\n",
      "INFO:root:Batch:       1 fc2_weight                     0.00577121\t\n",
      "INFO:root:Batch:       1 fc3_backward_weight            0.00490826\t\n",
      "INFO:root:Batch:       1 fc3_weight                     0.00581168\t\n",
      "INFO:root:Epoch[0] Batch [100]\tSpeed: 68004.57 samples/sec\tTrain-accuracy=0.141400\n",
      "INFO:root:Batch:     101 fc1_backward_weight            0.170696\t\n",
      "INFO:root:Batch:     101 fc1_weight                     0.0077417\t\n",
      "INFO:root:Batch:     101 fc2_backward_weight            0.300237\t\n",
      "INFO:root:Batch:     101 fc2_weight                     0.0188219\t\n",
      "INFO:root:Batch:     101 fc3_backward_weight            1.26234\t\n",
      "INFO:root:Batch:     101 fc3_weight                     0.0678799\t\n",
      "INFO:root:Epoch[0] Batch [200]\tSpeed: 63867.17 samples/sec\tTrain-accuracy=0.696600\n",
      "INFO:root:Batch:     201 fc1_backward_weight            0.224992\t\n",
      "INFO:root:Batch:     201 fc1_weight                     0.0224456\t\n",
      "INFO:root:Batch:     201 fc2_backward_weight            0.574649\t\n",
      "INFO:root:Batch:     201 fc2_weight                     0.0481841\t\n",
      "INFO:root:Batch:     201 fc3_backward_weight            1.50355\t\n",
      "INFO:root:Batch:     201 fc3_weight                     0.223626\t\n",
      "INFO:root:Epoch[0] Batch [300]\tSpeed: 63575.68 samples/sec\tTrain-accuracy=0.886700\n",
      "INFO:root:Batch:     301 fc1_backward_weight            0.128922\t\n",
      "INFO:root:Batch:     301 fc1_weight                     0.0297723\t\n",
      "INFO:root:Batch:     301 fc2_backward_weight            0.25938\t\n",
      "INFO:root:Batch:     301 fc2_weight                     0.0623646\t\n",
      "INFO:root:Batch:     301 fc3_backward_weight            0.623773\t\n",
      "INFO:root:Batch:     301 fc3_weight                     0.243092\t\n",
      "INFO:root:Epoch[0] Batch [400]\tSpeed: 63417.56 samples/sec\tTrain-accuracy=0.924800\n",
      "INFO:root:Batch:     401 fc1_backward_weight            0.23871\t\n",
      "INFO:root:Batch:     401 fc1_weight                     0.0343854\t\n",
      "INFO:root:Batch:     401 fc2_backward_weight            0.42189\t\n",
      "INFO:root:Batch:     401 fc2_weight                     0.0708286\t\n",
      "INFO:root:Batch:     401 fc3_backward_weight            0.808316\t\n",
      "INFO:root:Batch:     401 fc3_weight                     0.252618\t\n",
      "INFO:root:Epoch[0] Batch [500]\tSpeed: 63915.25 samples/sec\tTrain-accuracy=0.933600\n",
      "INFO:root:Batch:     501 fc1_backward_weight            0.189612\t\n",
      "INFO:root:Batch:     501 fc1_weight                     0.0384733\t\n",
      "INFO:root:Batch:     501 fc2_backward_weight            0.46391\t\n",
      "INFO:root:Batch:     501 fc2_weight                     0.0783045\t\n",
      "INFO:root:Batch:     501 fc3_backward_weight            1.06239\t\n",
      "INFO:root:Batch:     501 fc3_weight                     0.259909\t\n",
      "INFO:root:Epoch[0] Batch [600]\tSpeed: 63705.95 samples/sec\tTrain-accuracy=0.948200\n",
      "INFO:root:Epoch[0] Resetting Data Iterator\n",
      "INFO:root:Epoch[0] Time cost=0.945\n",
      "INFO:root:Epoch[0] Validation-accuracy=0.953100\n"
     ]
    }
   ],
   "source": [
    "def norm_stat(d):\n",
    "    \"\"\"The statistics you want to see.\n",
    "    We compute the L2 norm here but you can change it to anything you like.\"\"\"\n",
    "    return mx.nd.norm(d)/np.sqrt(d.size)\n",
    "mon = mx.mon.Monitor(\n",
    "    100,                 # Print every 100 batches\n",
    "    norm_stat,           # The statistics function defined above\n",
    "    pattern='.*weight',  # A regular expression. Only arrays with name matching this pattern will be included.\n",
    "    sort=True)           # Sort output by name\n",
    "model = mx.model.FeedForward(ctx = mx.gpu(0), symbol = mlp, num_epoch = 1,\n",
    "                             learning_rate = 0.1, momentum = 0.9, wd = 0.00001)\n",
    "model.fit(X=train_iter, eval_data=test_iter, monitor=mon,  # Set the monitor here\n",
    "          batch_end_callback = mx.callback.Speedometer(100, 100))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Under the hood: Custom Training Loop\n",
    "\n",
    "`mx.model.FeedForward` is a convenience wrapper for training standard feed forward networks. What if the model you are working with is more complicated? With MXNet, you can easily control every aspect of training by writing your own training loop.\n",
    "\n",
    "Neural network training typically has 3 steps: forward, backward (gradient), and update. With custom training loop, you can control the details in each step as while as insert complicated computations in between. You can also connect multiple networks together."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "input_shapes {'softmax_label': (100,), 'data': (100, 784L)}\n",
      "epoch: 0 iter: 100 metric: ('accuracy', 0.1427)\n",
      "epoch: 0 iter: 200 metric: ('accuracy', 0.42695)\n",
      "epoch: 0 iter: 300 metric: ('accuracy', 0.5826333333333333)\n",
      "epoch: 0 iter: 400 metric: ('accuracy', 0.66875)\n",
      "epoch: 0 iter: 500 metric: ('accuracy', 0.72238)\n",
      "epoch: 0 iter: 600 metric: ('accuracy', 0.7602166666666667)\n",
      "epoch: 1 iter: 100 metric: ('accuracy', 0.9504)\n",
      "epoch: 1 iter: 200 metric: ('accuracy', 0.9515)\n",
      "epoch: 1 iter: 300 metric: ('accuracy', 0.9547666666666667)\n",
      "epoch: 1 iter: 400 metric: ('accuracy', 0.956475)\n",
      "epoch: 1 iter: 500 metric: ('accuracy', 0.95762)\n",
      "epoch: 1 iter: 600 metric: ('accuracy', 0.9591666666666666)\n",
      "epoch: 2 iter: 100 metric: ('accuracy', 0.9681)\n",
      "epoch: 2 iter: 200 metric: ('accuracy', 0.9699)\n",
      "epoch: 2 iter: 300 metric: ('accuracy', 0.9718666666666667)\n",
      "epoch: 2 iter: 400 metric: ('accuracy', 0.97205)\n",
      "epoch: 2 iter: 500 metric: ('accuracy', 0.97282)\n",
      "epoch: 2 iter: 600 metric: ('accuracy', 0.9728833333333333)\n",
      "epoch: 3 iter: 100 metric: ('accuracy', 0.9776)\n",
      "epoch: 3 iter: 200 metric: ('accuracy', 0.97755)\n",
      "epoch: 3 iter: 300 metric: ('accuracy', 0.9782666666666666)\n",
      "epoch: 3 iter: 400 metric: ('accuracy', 0.977775)\n",
      "epoch: 3 iter: 500 metric: ('accuracy', 0.97842)\n",
      "epoch: 3 iter: 600 metric: ('accuracy', 0.9788666666666667)\n",
      "epoch: 4 iter: 100 metric: ('accuracy', 0.9804)\n",
      "epoch: 4 iter: 200 metric: ('accuracy', 0.98145)\n",
      "epoch: 4 iter: 300 metric: ('accuracy', 0.9818333333333333)\n",
      "epoch: 4 iter: 400 metric: ('accuracy', 0.981725)\n",
      "epoch: 4 iter: 500 metric: ('accuracy', 0.98214)\n",
      "epoch: 4 iter: 600 metric: ('accuracy', 0.9825333333333334)\n",
      "epoch: 5 iter: 100 metric: ('accuracy', 0.985)\n",
      "epoch: 5 iter: 200 metric: ('accuracy', 0.98415)\n",
      "epoch: 5 iter: 300 metric: ('accuracy', 0.9843333333333333)\n",
      "epoch: 5 iter: 400 metric: ('accuracy', 0.98425)\n",
      "epoch: 5 iter: 500 metric: ('accuracy', 0.9846)\n",
      "epoch: 5 iter: 600 metric: ('accuracy', 0.9846833333333334)\n",
      "epoch: 6 iter: 100 metric: ('accuracy', 0.9848)\n",
      "epoch: 6 iter: 200 metric: ('accuracy', 0.98445)\n",
      "epoch: 6 iter: 300 metric: ('accuracy', 0.9853333333333333)\n",
      "epoch: 6 iter: 400 metric: ('accuracy', 0.98555)\n",
      "epoch: 6 iter: 500 metric: ('accuracy', 0.9857)\n",
      "epoch: 6 iter: 600 metric: ('accuracy', 0.9862166666666666)\n",
      "epoch: 7 iter: 100 metric: ('accuracy', 0.9868)\n",
      "epoch: 7 iter: 200 metric: ('accuracy', 0.9858)\n",
      "epoch: 7 iter: 300 metric: ('accuracy', 0.9867333333333334)\n",
      "epoch: 7 iter: 400 metric: ('accuracy', 0.986825)\n",
      "epoch: 7 iter: 500 metric: ('accuracy', 0.98674)\n",
      "epoch: 7 iter: 600 metric: ('accuracy', 0.9869166666666667)\n",
      "epoch: 8 iter: 100 metric: ('accuracy', 0.9883)\n",
      "epoch: 8 iter: 200 metric: ('accuracy', 0.98825)\n",
      "epoch: 8 iter: 300 metric: ('accuracy', 0.9886666666666667)\n",
      "epoch: 8 iter: 400 metric: ('accuracy', 0.989075)\n",
      "epoch: 8 iter: 500 metric: ('accuracy', 0.98954)\n",
      "epoch: 8 iter: 600 metric: ('accuracy', 0.9896)\n",
      "epoch: 9 iter: 100 metric: ('accuracy', 0.9894)\n",
      "epoch: 9 iter: 200 metric: ('accuracy', 0.98945)\n",
      "epoch: 9 iter: 300 metric: ('accuracy', 0.9893333333333333)\n",
      "epoch: 9 iter: 400 metric: ('accuracy', 0.98935)\n",
      "epoch: 9 iter: 500 metric: ('accuracy', 0.98972)\n",
      "epoch: 9 iter: 600 metric: ('accuracy', 0.9903166666666666)\n"
     ]
    }
   ],
   "source": [
    "# ==================Binding=====================\n",
    "# The symbol we created is only a graph description.\n",
    "# To run it, we first need to allocate memory and create an executor by 'binding' it.\n",
    "# In order to bind a symbol, we need at least two pieces of information: context and input shapes.\n",
    "# Context specifies which device the executor runs on, e.g. cpu, GPU0, GPU1, etc.\n",
    "# Input shapes define the executor's input array dimensions.\n",
    "# MXNet then run automatic shape inference to determine the dimensions of intermediate and output arrays.\n",
    "\n",
    "# data iterators defines shapes of its output with provide_data and provide_label property.\n",
    "input_shapes = dict(train_iter.provide_data+train_iter.provide_label)\n",
    "print 'input_shapes', input_shapes\n",
    "# We use simple_bind to let MXNet allocate memory for us.\n",
    "# You can also allocate memory youself and use bind to pass it to MXNet.\n",
    "exe = mlp.simple_bind(ctx=mx.gpu(0), **input_shapes)\n",
    "\n",
    "# ===============Initialization=================\n",
    "# First we get handle to input arrays\n",
    "arg_arrays = dict(zip(mlp.list_arguments(), exe.arg_arrays))\n",
    "data = arg_arrays[train_iter.provide_data[0][0]]\n",
    "label = arg_arrays[train_iter.provide_label[0][0]]\n",
    "\n",
    "# We initialize the weights with uniform distribution on (-0.01, 0.01).\n",
    "init = mx.init.Uniform(scale=0.01)\n",
    "for name, arr in arg_arrays.items():\n",
    "    if name not in input_shapes:\n",
    "        init(name, arr)\n",
    "    \n",
    "# We also need to create an optimizer for updating weights\n",
    "opt = mx.optimizer.SGD(\n",
    "    learning_rate=0.1,\n",
    "    momentum=0.9,\n",
    "    wd=0.00001,\n",
    "    rescale_grad=1.0/train_iter.batch_size)\n",
    "updater = mx.optimizer.get_updater(opt)\n",
    "\n",
    "# Finally we need a metric to print out training progress\n",
    "metric = mx.metric.Accuracy()\n",
    "\n",
    "# Training loop begines\n",
    "for epoch in range(10):\n",
    "    train_iter.reset()\n",
    "    metric.reset()\n",
    "    t = 0\n",
    "    for batch in train_iter:\n",
    "        # Copy data to executor input. Note the [:].\n",
    "        data[:] = batch.data[0]\n",
    "        label[:] = batch.label[0]\n",
    "        \n",
    "        # Forward\n",
    "        exe.forward(is_train=True)\n",
    "        \n",
    "        # You perform operations on exe.outputs here if you need to.\n",
    "        # For example, you can stack a CRF on top of a neural network.\n",
    "        \n",
    "        # Backward\n",
    "        exe.backward()\n",
    "        \n",
    "        # Update\n",
    "        for i, pair in enumerate(zip(exe.arg_arrays, exe.grad_arrays)):\n",
    "            weight, grad = pair\n",
    "            updater(i, grad, weight)\n",
    "        metric.update(batch.label, exe.outputs)\n",
    "        t += 1\n",
    "        if t % 100 == 0:\n",
    "            print 'epoch:', epoch, 'iter:', t, 'metric:', metric.get()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## New Operators\n",
    "\n",
    "MXNet provides a repository of common operators (or layers). However, new models often require new layers. There are several ways to [create new operators](https://mxnet.readthedocs.org/en/latest/tutorial/new_op_howto.html) with MXNet. Here we talk about the easiest way: pure python. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:root:Start training with [gpu(0)]\n",
      "INFO:root:Epoch[0] Batch [100]\tSpeed: 37586.20 samples/sec\tTrain-accuracy=0.167800\n",
      "INFO:root:Epoch[0] Batch [200]\tSpeed: 36706.65 samples/sec\tTrain-accuracy=0.743800\n",
      "INFO:root:Epoch[0] Batch [300]\tSpeed: 36625.21 samples/sec\tTrain-accuracy=0.896900\n",
      "INFO:root:Epoch[0] Batch [400]\tSpeed: 37129.04 samples/sec\tTrain-accuracy=0.929000\n",
      "INFO:root:Epoch[0] Batch [500]\tSpeed: 38473.68 samples/sec\tTrain-accuracy=0.938100\n",
      "INFO:root:Epoch[0] Batch [600]\tSpeed: 39935.29 samples/sec\tTrain-accuracy=0.946400\n",
      "INFO:root:Epoch[0] Resetting Data Iterator\n",
      "INFO:root:Epoch[0] Time cost=1.600\n",
      "INFO:root:Epoch[0] Validation-accuracy=0.950400\n",
      "INFO:root:Epoch[1] Batch [100]\tSpeed: 39941.34 samples/sec\tTrain-accuracy=0.946700\n",
      "INFO:root:Epoch[1] Batch [200]\tSpeed: 39685.38 samples/sec\tTrain-accuracy=0.952700\n",
      "INFO:root:Epoch[1] Batch [300]\tSpeed: 40096.55 samples/sec\tTrain-accuracy=0.960800\n",
      "INFO:root:Epoch[1] Batch [400]\tSpeed: 39397.53 samples/sec\tTrain-accuracy=0.960200\n",
      "INFO:root:Epoch[1] Batch [500]\tSpeed: 38451.49 samples/sec\tTrain-accuracy=0.965000\n",
      "INFO:root:Epoch[1] Batch [600]\tSpeed: 39442.28 samples/sec\tTrain-accuracy=0.968600\n",
      "INFO:root:Epoch[1] Resetting Data Iterator\n",
      "INFO:root:Epoch[1] Time cost=1.527\n",
      "INFO:root:Epoch[1] Validation-accuracy=0.953400\n"
     ]
    }
   ],
   "source": [
    "# Define custom softmax operator\n",
    "class NumpySoftmax(mx.operator.NumpyOp):\n",
    "    def __init__(self):\n",
    "        # Call the parent class constructor. \n",
    "        # Because NumpySoftmax is a loss layer, it doesn't need gradient input from layers above.\n",
    "        super(NumpySoftmax, self).__init__(need_top_grad=False)\n",
    "    \n",
    "    def list_arguments(self):\n",
    "        # Define the input to NumpySoftmax.\n",
    "        return ['data', 'label']\n",
    "\n",
    "    def list_outputs(self):\n",
    "        # Define the output.\n",
    "        return ['output']\n",
    "\n",
    "    def infer_shape(self, in_shape):\n",
    "        # Calculate the dimensions of the output (and missing inputs) from (some) input shapes.\n",
    "        data_shape = in_shape[0]  # shape of first argument 'data'\n",
    "        label_shape = (in_shape[0][0],)  # 'label' should be one dimensional and has batch_size instances.\n",
    "        output_shape = in_shape[0] # 'output' dimension is the same as the input.\n",
    "        return [data_shape, label_shape], [output_shape]\n",
    "\n",
    "    def forward(self, in_data, out_data):\n",
    "        x = in_data[0]  # 'data'\n",
    "        y = out_data[0]  # 'output'\n",
    "        \n",
    "        # Compute softmax\n",
    "        y[:] = np.exp(x - x.max(axis=1).reshape((x.shape[0], 1)))\n",
    "        y /= y.sum(axis=1).reshape((x.shape[0], 1))\n",
    "\n",
    "    def backward(self, out_grad, in_data, out_data, in_grad):\n",
    "        l = in_data[1]  # 'label'\n",
    "        l = l.reshape((l.size,)).astype(np.int)  # cast to int\n",
    "        y = out_data[0]  # 'output'\n",
    "        dx = in_grad[0]  # gradient for 'data'\n",
    "        \n",
    "        # Compute gradient\n",
    "        dx[:] = y\n",
    "        dx[np.arange(l.shape[0]), l] -= 1.0\n",
    "\n",
    "numpy_softmax = NumpySoftmax()\n",
    "\n",
    "data = mx.symbol.Variable('data')\n",
    "fc1 = mx.symbol.FullyConnected(data = data, name='fc1', num_hidden=128)\n",
    "act1 = mx.symbol.Activation(data = fc1, name='relu1', act_type=\"relu\")\n",
    "fc2 = mx.symbol.FullyConnected(data = act1, name = 'fc2', num_hidden = 64)\n",
    "act2 = mx.symbol.Activation(data = fc2, name='relu2', act_type=\"relu\")\n",
    "fc3 = mx.symbol.FullyConnected(data = act2, name='fc3', num_hidden=10)\n",
    "# Use the new operator we just defined instead of the standard softmax operator.\n",
    "mlp = numpy_softmax(data=fc3, name = 'softmax')\n",
    "\n",
    "model = mx.model.FeedForward(ctx = mx.gpu(0), symbol = mlp, num_epoch = 2,\n",
    "                             learning_rate = 0.1, momentum = 0.9, wd = 0.00001)\n",
    "model.fit(X=train_iter, eval_data=test_iter,\n",
    "          batch_end_callback = mx.callback.Speedometer(100, 100))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## Further Readings\n",
    "\n",
    "- More state-of-the-art cnn models are available at [mxnet/example/image-classification](https://github.com/dmlc/mxnet/tree/master/example/image-classification)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
